﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace MyMailWp8.Protocol
{
    public enum SockMessageType
    {
        // Summary:
        ConnectSucess = 1,
        SendSucess = 2,
        ReceiveSucess = 3,
        ConnectFail = 101,
        LoginFail = 102,
        ListFail = 103,
        UidlFail = 104,
        SendFail = 105,
        ReceiveFail = 106,
        DataError = 107,
    };


    public class AsyncSocketClient
    {
        // The port number for the remote device.
        private int _port = 0;
        private string _serverName = string.Empty;

        // A timeout for all socket communication
        private const int TIMEOUT_MILLISECONDS = 3000;

        private bool isConnected = false;

        // Notify the caller or user of this class through a custom event
        internal event ResponseReceivedHandler ResponseReceived;

        private Socket _socket = null;

        private byte[] recvBuff = new byte[10240];  //接收缓冲区 暂定大小1M

        private System.Object lockObject = new System.Object();

        public AsyncSocketClient(string serverName, int portNumber)
        {
            if (String.IsNullOrWhiteSpace(serverName))
            {
                throw new ArgumentNullException("serverName");
            }

            if (portNumber < 0 || portNumber > 65535)
            {
                throw new ArgumentNullException("portNumber");
            }

            _serverName = serverName;
            _port = portNumber;
        }


        public void ConnectToSever()
        {
            if (isConnected)
                return;

            SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
            try
            {
                DnsEndPoint hostEntry = new DnsEndPoint(_serverName, _port);
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);
                socketEventArg.RemoteEndPoint = hostEntry;
            }
            catch (ArgumentException ex)
            {
                throw ex;
            }

            // Create a socket and connect to the server 
            try
            {
                bool ret = _socket.ConnectAsync(socketEventArg);
                ret = false;
            }
            catch (SocketException ex)
            {
                throw ex;
            }
        }


        public void DisConnectToSever()
        {
            if (!isConnected)
                return;
            try
            {
                Socket sock = _socket;
                sock.Shutdown(SocketShutdown.Send);
                sock.Close();
            }
            catch (Exception e)
            {
                throw e;
            }
            isConnected = false;
        }


        /// <summary>
        /// Send data to the server
        /// </summary>
        /// <param name="data">The data to send</param>
        /// <remarks> This is an asynchronous call, with the result being passed to the callee
        /// through the ResponseReceived event</remarks>
        public void SendData(string data)
        {
            if (isConnected == false)
            {
                throw new PESocketException((int)PESocketErrCode.NOT_CONNECT);
            }

            if (String.IsNullOrEmpty(data))
            {
                throw new PESocketException((int)PESocketErrCode.SEND_BUFFER_ERR);
            }
            try
            {
                // Send data to the server 
                SocketAsyncEventArgs e = new SocketAsyncEventArgs();
                e.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);

                byte[] buffer = Encoding.UTF8.GetBytes(data);
                e.SetBuffer(buffer, 0, buffer.Length);

                _socket.SendAsync(e);
            }
            catch (SocketException ex)
            {
                throw new PESocketException((int)ex.SocketErrorCode);
                // SocketError 
            }
        }

        public void SendByteData(byte[] buffer)
        {
            if (isConnected == false)
            {
                throw new PESocketException((int)PESocketErrCode.NOT_CONNECT);
            }

            if (buffer == null)
            {
                throw new PESocketException((int)PESocketErrCode.SEND_BUFFER_ERR);
            }
            try
            {
                // Send data to the server 
                SocketAsyncEventArgs e = new SocketAsyncEventArgs();
                e.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);
                e.SetBuffer(buffer, 0, buffer.Length);

                _socket.SendAsync(e);
            }
            catch (SocketException ex)
            {
                throw new PESocketException((int)ex.SocketErrorCode);
            }
        }

        public void ReceiveData()
        {
            if (isConnected == false)
            {
                throw new PESocketException((int)PESocketErrCode.NOT_CONNECT);
            }
            try
            {
                //Read data sent from the server 
                // Create SocketAsyncEventArgs context object
                SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
                socketEventArg.RemoteEndPoint = _socket.RemoteEndPoint;
                socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);

                // Setup the buffer to receive the data
                lock (lockObject)
                {
                    socketEventArg.SetBuffer(recvBuff, 0, recvBuff.Length);
                }

                _socket.ReceiveAsync(socketEventArg);

            }
            catch (SocketException ex)
            {
                throw new PESocketException((int)ex.SocketErrorCode);
            }
        }

        #region

        // A single callback is used for all socket operations. 
        // This method forwards execution on to the correct handler 
        // based on the type of completed operation 
        private void SocketEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    ProcessConnect(e);
                    break;

                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;

                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;
            }
        }

        // Called when a ReceiveAsync operation completes  
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            ResponseReceivedEventArgs args = new ResponseReceivedEventArgs();
            if (e.SocketError == SocketError.Success)
            {
                // Received data from server 
                //yzf2012.0.11 目前处理对于二进制数据有问题,需要调�?
                string dataFromServer = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);

                // Respond to the client in the UI thread to tell him that data was received
                //System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    //2012-4-24 ResponseReceivedEventArgs args = new ResponseReceivedEventArgs();
                    args.nMessageType = (int)SockMessageType.ReceiveSucess;
                    args.response = dataFromServer;
                    //2012-4-24 ResponseReceived(args);
                }
                //);
            }
            else
            {
                // DefaultLogger.Log.LogDebug("socket receive throw Exception\r\n");
                //2012-4-24这个地方抛出异常没地方捕获
                //2012-4-24throw new PESocketException((int)e.SocketError);
                args.nMessageType = (int)SockMessageType.ReceiveFail;
            }
            OnResponseReceived(args);
        }


        // Called when a SendAsync operation completes 
        private void ProcessSend(SocketAsyncEventArgs e)
        {
            ResponseReceivedEventArgs args = new ResponseReceivedEventArgs();
            if (e.SocketError == SocketError.Success)
            {
                args.nMessageType = (int)SockMessageType.SendSucess;
            }
            else
            {
                args.nMessageType = (int)SockMessageType.SendFail;
            }
            OnResponseReceived(args);
        }

        // Called when a ConnectAsync operation completes 
        private void ProcessConnect(SocketAsyncEventArgs e)
        {
            ResponseReceivedEventArgs args = new ResponseReceivedEventArgs();
            if (e.SocketError == SocketError.Success)
            {
                // Successfully connected to the server 
                isConnected = true;

                args.nMessageType = (int)SockMessageType.ConnectSucess;
            }
            else
            {
                args.nMessageType = (int)SockMessageType.ConnectFail;

                //DefaultLogger.Log.LogDebug("socket connect fail\r\n");
            }

            OnResponseReceived(args);
        }
        #endregion

        // Invoke the ResponseReceived event
        protected void OnResponseReceived(ResponseReceivedEventArgs e)
        {
            if (ResponseReceived != null)
                ResponseReceived(this, e);
        }

    }//Cls

    // A delegate type for hooking up change notifications.
    public delegate void ResponseReceivedHandler(object sender, ResponseReceivedEventArgs e);

    public class ResponseReceivedEventArgs : EventArgs
    {
        public int nMessageType;//sucess 1-connect,2-send,3-read,fail 101-connect,102-send,103-read

        // If there was an erro, this will contain the error message, data otherwise
        public string response { get; set; }
    }

}

